﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore.ValueGeneration.Internal;
using Microsoft.Extensions.DependencyInjection;
using netCoreStudy.Entities;
using netCoreStudy.Entities.Base;
using netCoreStudy.IService;

namespace netCoreStudy.Auth
{
	public class PermissionHandler : AuthorizationHandler<PermissionRequirement>
	{
		private readonly IAuthenticationSchemeProvider _schemes;
		private readonly IHttpContextAccessor _accessor;
		private readonly IRoleService _roleService;

		public PermissionHandler(IAuthenticationSchemeProvider schemes,
			IHttpContextAccessor accessor,
			IRoleService roleService)
		{
			_schemes = schemes;
			_accessor = accessor;
			_roleService = roleService;
		}

		protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context,
			PermissionRequirement requirement)
		{
			HttpContext httpContext = _accessor.HttpContext;
			List<PermissionItem> list = new List<PermissionItem>();
			Console.WriteLine("嘟嘟");
			if (!requirement.Permissions.Any())
			{
				PageList<Role> roles =
					await _roleService.GetPages(new BaseParameter() {PageSize = 1000});
				IEnumerable<RoleMenu> roleMenus =
					roles.Select(x => x.RoleMenus).SelectMany(x => x.ToArray());
				list = roleMenus.Select(x => new PermissionItem
						{RoleId = x.Role.Id, RoleName = x.Role.Name, Url = x.Menu.Path})
					.Where(x => x.Url != null)
					.ToList();
				// 将 数据库中的 所以 menu 都塞进这个 requirement 里面 去
				requirement.Permissions = list;
			}

			if (httpContext != null)
			{
				// 获取请求 api  如 "api/company"
				string reqUrl = httpContext.Request.Path.Value.ToLower();
				httpContext.Features.Set<IAuthenticationFeature>(new AuthenticationFeature()
				{
					OriginalPath = httpContext.Request.Path,
					OriginalPathBase = httpContext.Request.PathBase
				});
				// 主要作用是: 判断当前是否需要进行远程验证，如果是就进行远程验证	
				// 这是官方认证中间件的源码  ( 还不懂…… )
				IAuthenticationHandlerProvider handlers = httpContext.RequestServices
					.GetRequiredService<IAuthenticationHandlerProvider>();
				IEnumerable<AuthenticationScheme> schemes =
					await _schemes.GetRequestHandlerSchemesAsync();
				foreach (var scheme in schemes)
				{
					if (await handlers.GetHandlerAsync(httpContext, scheme.Name) is
						    IAuthenticationRequestHandler handler &&
					    await handler.HandleRequestAsync())
					{
						context.Fail();
						return;
					}
				}

				// 判断请求是否拥有凭据 ,即有没有登录 
				AuthenticationScheme defaultAuthenticate =
					await _schemes.GetDefaultAuthenticateSchemeAsync();
				if (defaultAuthenticate != null)
				{
					AuthenticateResult result =
						await httpContext.AuthenticateAsync(defaultAuthenticate.Name);
					// result.Principal 不为空即 为登录成功
					//  为空即 为 token 过期 或者 没有 token (或 token 无效 )
					// result.Principal 里面有 claims 的一些 信息 还有别的
					if (result.Principal != null)
					{
						// 这两个貌似是一样的…… 不知道为啥要覆盖 
						httpContext.User = result.Principal;
						// 获取当前用户的角色信息
						List<long> currentUserRoles = httpContext.User.Claims
							.Where(x => x.Type == requirement.ClaimType)
							.Select(x => long.Parse(x.Value))
							.ToList();
						bool isMatchRole = false;
						// 从传进来 的 token 里面 获取 role 和 存储 的 roleMenus 对比 ,找到 符合 role 的 menu 列表 
						IEnumerable<PermissionItem> permissionRoles =
							requirement.Permissions.Where(x => currentUserRoles.Contains(x.RoleId));
						foreach (PermissionItem item in permissionRoles)
						{
							// 请求 的 url(1个)  和 menu 列表 里面 的 url 比较 
							// 只要符合就说明这个请求 可以 过 (符合这个角色的权限) 
							if (reqUrl.Contains(item.Url.ToLower()))
							{
								isMatchRole = true;
								break;
							}
						}

						// 验证权限 
						//如果 根据 token 里面 找不到对应 的 角色列表 或者 没有符合 角色 的menu 就 不给过
						if (currentUserRoles.Count <= 0 || !isMatchRole)
						{
							context.Fail();
						}

						bool isExp = false; // 是否过期 
						//先找到 token 里面 的过期日期 
						Claim expTime = httpContext.User.Claims
							.SingleOrDefault(x => x.Type == "exp");
						//如果 有过期时间 并且 过期时间 大于当前 时间
						if (expTime != null && expTime.Value.ConvertToDateTime() >= DateTime.Now)
						{
							context.Succeed(requirement);
						}
						else
						{
							context.Fail();
							return;
						}

						return;
					}
				}
			}
		}
	}
}